// C source file with functions to deal with floats

/******************************************
* function to print bean default float type
*******************************************/

// print_fmax_num() function
umax print_fmax_num(void * ptr, umax size) {
  if (size < sizeof(fmax))
    return 0;
  printf("%lf", *((fmax *) ptr));
  return size;
}

/**********************************************
* useful math functions that use the float type
***********************************************/

// get_abs() function
// function to get the magnitude of a number
fmax get_abs(fmax number)
{
  if (number < 0)
    number = (-1) * number;
  return number;
}


// powerof_float_to_sintloat() function
// function to get the power of a 
// float elevated to a signed integer
fmax powerof_float_to_sint(fmax base, smax exp)
{
  // base equals 0 (bruh)
  if (base == 0)
    return 0;
  // exp equals 0 (bruh)
  if (exp == 0)
    return 1;
  
  // variable to return
  fmax result = 0;
  
  // otherwise, calculate base^(exp)
  if (exp > 0) /* positive exp */ {
    result = base;
    for (smax i = 2; i <= exp; i++)
      result *= base;
  }
  else if (exp < 0) /* negative exp */ {
    result = 1 / base;
    for (smax i = -2; i >= exp; i--)
      result *= 1 / base;
  }  
  // done
  return result;
}

// get_floor() function
// function to get the closest lower integer of a number 
// kinda inneficient tbh, will see if I can get a better method
fmax get_floor(fmax number)
{
  // I wont do a cast to an integer type 
  // because it isn't a complete solution
  // (overflow issue)
  fmax tmp1, tmp2 = get_abs(number);
  umax ten_exp = 0, max_mult = 0;
  while (tmp2 > 0) {
    // get ten exp from tmp1
    ten_exp = 0;
    // tmp1 will be used to count the scietific notation exp from tmp2
    // tmp2 will be used to get the decimal portion of number. This is done by
    // substracting power of 10 integers from it (depending on ten_exp) until
    // it satisfies 0 < tmp2 < 1
    tmp1 = tmp2;
    while (tmp1 >= 10) {
      tmp1 /= 10;
      ten_exp++;
    }
    
    // max_mult will be used to find the lowest
    // integer number that tmp1 is closer to
    if (tmp1 > 0 && tmp1 < 1)
      max_mult = 1;
    else if (tmp1 >= 1 && tmp1 < 2)
      max_mult = 1;
    else if (tmp1 >= 2 && tmp1 < 3)
      max_mult = 2;
    else if (tmp1 >= 3 && tmp1 < 4)
      max_mult = 3;
    else if (tmp1 >= 4 && tmp1 < 5)
      max_mult = 4;
    else if (tmp1 >= 5 && tmp1 < 6)
      max_mult = 5;
    else if (tmp1 >= 6 && tmp1 < 7)
      max_mult = 6;
    else if (tmp1 >= 7 && tmp1 < 8)
      max_mult = 7;
    else if (tmp1 >= 8 && tmp1 < 9)
      max_mult = 8;
    else if (tmp1 >= 9 && tmp1 < 10)
      max_mult = 9;
    
    //~ printf("%f %f\n", tmp2, (max_mult * powerof_float_to_sint(10, ten_exp)));    
    // substract 10^(ten_exp) from tmp2
    // until it becomes less than 1 (positive)
    if (tmp2 > 1)
      tmp2 = tmp2 - (max_mult * powerof_float_to_sint(10, ten_exp));
    else // return the difference of number and tmp2
      return number > 0 ? number - tmp2 : number + tmp2 - 1;
  }
  
  // return number in case it is already its floor value
  return number;
}

// get_ceil() function
// function to get the closest upper integer of a number
fmax get_ceil(fmax number)
{
  // using the get_floor() function
  fmax result = get_floor(number);
  return number == result ? number : result + 1;
}


/******************************************************************************
* functions to calculate some types of floats from bits in the BIT_HOLDER array
*******************************************************************************/

// bits_to_ieee754_float() function
// function to calculate an IEEE 754 float (16, 32 and 64 bits) using the "normal"
// calculation method. It will read the bits on the BIT_HOLDER array.
fmax bits_to_ieee754_float(umax bit_holder_pos)
{  
  // float components
  char sign = 0;
  smax exp = 0;
  fmax frac = 0;
  
  // create the variables for the float type to be calculated
  byte biased_exp_bits = 0;
  byte stored_frac_bits = 0;
  if (BIT_HOLDER_LENGTH - bit_holder_pos == 16) /* half-precision */ {
    // https://en.wikipedia.org/wiki/Half-precision_floating-point_format
    biased_exp_bits = 5;
    stored_frac_bits = 10;
  }
  else if (BIT_HOLDER_LENGTH - bit_holder_pos == 32) /* single-precision */ {
    // https://en.wikipedia.org/wiki/Single-precision_floating-point_format
    biased_exp_bits = 8;
    stored_frac_bits = 23;
  }
  else if (BIT_HOLDER_LENGTH - bit_holder_pos == 64) /* double-precision */ {
    // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
    biased_exp_bits = 11;
    stored_frac_bits = 52;
  }
  else // the float bit size is not supported
    return 0.0f;
  
  // sign (always 1 bit)
  sign = BIT_HOLDER[bit_holder_pos] == 1 ? -1 : +1;  
  // calculate base 2 biased exp (biased_exp_bits)
  byte_n_circ_shift(BIT_HOLDER, BIT_HOLDER_LENGTH, BYTESHIFT_RIGHT,
                    BIT_HOLDER_LENGTH - bit_holder_pos - (biased_exp_bits + 1));
  exp = bits_to_off_bin_sint(BIT_HOLDER_LENGTH - biased_exp_bits);
  byte_n_circ_shift(BIT_HOLDER, BIT_HOLDER_LENGTH, BYTESHIFT_LEFT,
                    BIT_HOLDER_LENGTH - bit_holder_pos - (biased_exp_bits + 1));  
  // frac part (10 bits)
  frac = bits_to_std_uint(bit_holder_pos + biased_exp_bits + 1) / powerof_float_to_sint(2, stored_frac_bits);
  
  // exp cases
  // the biased exp is literally 0
  if (exp == (- powerof_sint(2, biased_exp_bits - 1)))
    exp = (- powerof_sint(2, biased_exp_bits - 1)) + 1;
  else /* the biased exp is non-zero */ {
    // if the exp is 111...1 (infinity consideration)
    if (exp == powerof_sint(2, biased_exp_bits) - 1)
      return sign == +1 ? (+1 / 0.0f) : (-1 / 0.0f);
    else // number is good to go
      frac = 1 + frac;
  }  
  // return the result
  return sign * (frac * powerof_float_to_sint(2, exp));
}

/********************************************************************
* functions to calculate different type of floats from bits in memory
*********************************************************************/

// calc_mem_ieee754_float() function
// function to get a standard float number (of a specific bit size)
// from memory by manually calculating its value from bits
fmax calc_mem_ieee754_float(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  // get the float number if the bit size is valid (16, 32, and 64)
  return bits_to_ieee754_float(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

/*******************************************************************
* function to convert a byte aligned float type in memory to another
* type. IEEE 754 floating point format types (half, single, double)
*******************************************************************/

// convert_float() function
// function to convert a IEEE 754 floating point number from memory (byte aligned)
// to another float type (half, single, double). Assumes CHAR_BIT is 8.
bool convert_float(umax src_bit_cnt, void * src, ENDIAN_T src_endian,
                     umax dest_bit_cnt, void * dest, ENDIAN_T dest_endian)
{  
  // check params
  if (((src_bit_cnt % 16) != 0 || src_bit_cnt > 64 )
      || ((dest_bit_cnt % 16) != 0 || dest_bit_cnt > 64 )
      || src == NULL || dest == NULL)
      return false;
  
  // define the src and dest float constants
  umax src_exp_bias, dest_exp_bias;
  byte sign_bits = 1, src_exp_bits, src_frac_bits, dest_exp_bits, dest_frac_bits;
  byte src_byte_cnt = src_bit_cnt / 8, dest_byte_cnt = dest_bit_cnt / 8; 
  
  // for src
  if (src_bit_cnt == 16) {
    src_exp_bits = 5;
    src_frac_bits = 10;
    src_exp_bias = 15;
  } else if (src_bit_cnt == 32) {
    src_exp_bits = 8;
    src_frac_bits = 23;
    src_exp_bias = 127;
  } else /* src_bit_cnt == 64 */ {
    src_exp_bits = 11;
    src_frac_bits = 52;
    src_exp_bias = 1023;
  }  
  // for dest
  if (dest_bit_cnt == 16) {
    dest_exp_bits = 5;
    dest_frac_bits = 10;
    dest_exp_bias = 15;
  } else if (dest_bit_cnt == 32) {
    dest_exp_bits = 8;
    dest_frac_bits = 23;
    dest_exp_bias = 127; 
  } else /* dest_bit_cnt == 64 */ {
    dest_exp_bits = 11;
    dest_frac_bits = 52;
    dest_exp_bias = 1023;
  }
  
  // process each component of the float
  byte sign[8] = {0}, exp[8] = {0}, frac[8] = {0};
  
  // get the sign bits
  get_byte_bits(src_byte_cnt, 0, sign_bits, 0, src, src_endian); 
  sign[0] = BYTE_HOLDER[BYTE_HOLDER_LENGTH - 1] << 7;
  // get the biased exponent in the dest representation and adjust the bits position
  umax tmp = calc_mem_off_bin_sint(src_byte_cnt, 0, src_exp_bits, sign_bits, src, src_endian) + dest_exp_bias;
  if (SYS_ENDIAN == LITTLE)
    reverse_array(&tmp, sizeof(umax), 1);
  cp_mem_bytes(&tmp, sizeof(umax), exp + 8 - sizeof(umax));
  arr_n_bitshift(exp, 8, 1, BITSHIFT_LEFT, (64 - dest_exp_bits));
  arr_bitshift(exp, 8, 1, BITSHIFT_RIGHT);
  // get the fraction bits and adjust them
  get_byte_bits(src_byte_cnt, 0, src_frac_bits, src_exp_bits + sign_bits, src, src_endian);
  cp_mem_bytes(BYTE_HOLDER + BYTE_HOLDER_LENGTH - 8, 8, frac);
  arr_n_bitshift(frac, 8, 1, BITSHIFT_LEFT, 64 - src_frac_bits - dest_exp_bits - sign_bits);
  
  // put everything into dest
  byte * ptr = dest;
  for (byte i = 0; i < dest_byte_cnt; i++)
    ptr[i] = sign[i] + exp[i] + frac[i];  
  if (dest_endian == LITTLE)
    reverse_array(ptr, dest_byte_cnt, 1);  
  return true;
}


/************************************************************
* function to get usual floats from memory (not byte aligned)
* and get it inside a fmax (which I assume it is a double)
************************************************************/

// get_float() function
// function to get a IEEE 754 floating point number from memory (byte aligned)
// it is a special case for convert_float() as I only want the value
fmax get_float(umax bit_cnt, void * src, ENDIAN_T endian)
{
  // variable to return
  fmax result = 0;
  if (sizeof(fmax) == 8)
    convert_float(bit_cnt, src, endian, 64, &result, SYS_ENDIAN);
  else if (sizeof(fmax) == 4)
    convert_float(bit_cnt, src, endian, 32, &result, SYS_ENDIAN);
  return result;
}

// print std floats

// print_float16be() function
umax print_float16be(void * ptr, umax size)
{
  if (size < 2)
    return 0;
  printf("%lf", get_float(16, ptr, BIG));
  return size;
}

// print_float16le() function
umax print_float16le(void * ptr, umax size)
{
  if (size < 2)
    return 0;
  printf("%lf", get_float(16, ptr, LITTLE));
  return size;
}

// print_float32be() function
umax print_float32be(void * ptr, umax size)
{
  if (size < 4)
    return 0;
  printf("%lf", get_float(32, ptr, BIG));
  return size;
}

// print_float32le() function
umax print_float32le(void * ptr, umax size)
{
  if (size < 4)
    return 0;
  printf("%lf", get_float(32, ptr, LITTLE));
  return size;
}

// print_float64be() function
umax print_float64be(void * ptr, umax size)
{
  if (size < 8)
    return 0;
  printf("%lf", get_float(64, ptr, BIG));
  return size;
}

// print_float64le() function
umax print_float64le(void * ptr, umax size)
{
  if (size < 8)
    return 0;
  printf("%lf", get_float(64, ptr, LITTLE));
  return size;
}
